[id].vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. <template>
  2. <div class="admin--page-content">
  3. <form @submit.prevent="handleSubmit" class="admin--form">
  4. <!-- 낚시분야 -->
  5. <table class="admin--form--table">
  6. <colgroup>
  7. <col style="width: 120px;">
  8. <col>
  9. </colgroup>
  10. <tbody>
  11. <tr>
  12. <th>
  13. <div>
  14. 분야 <span class="admin--required">*</span>
  15. </div>
  16. </th>
  17. <td>
  18. <div class="input--wrap">
  19. <input
  20. v-model="formData.name"
  21. type="text"
  22. class="admin--form-input"
  23. placeholder="예: 갯바위낚시"
  24. required
  25. />
  26. <p>낚시 종류 분류명. 1~30자 이내. 중복 불가</p>
  27. </div>
  28. </td>
  29. </tr>
  30. <tr>
  31. <th>
  32. <div>
  33. 가중치 <span class="admin--required">*</span>
  34. </div>
  35. </th>
  36. <td>
  37. <div class="input--wrap">
  38. <input
  39. v-model="formData.weight"
  40. type="text"
  41. class="admin--form-input w--120"
  42. placeholder="0.0 ~ 1.0"
  43. required
  44. />
  45. <p>0.0 ~ 1.0 사이 소수점 1자리 (최대값 1.0 = 100% 확률). 물고기 잡을 때 아이템 지급 확률</p>
  46. </div>
  47. </td>
  48. </tr>
  49. <tr>
  50. <th><div>상태 <span class="admin--required">*</span></div></th>
  51. <td>
  52. <div class="input--wrap">
  53. <label class="admin--radio-label">
  54. <input type="radio" v-model="formData.status_YN" value="Y" /> 사용중
  55. </label>
  56. <label class="admin--radio-label ml--16">
  57. <input type="radio" v-model="formData.status_YN" value="N" /> 미사용
  58. </label>
  59. </div>
  60. </td>
  61. </tr>
  62. </tbody>
  63. </table>
  64. <div class="admin--info--box">
  65. <h3>💡 가중치 안내</h3>
  66. <ul>
  67. <li>가중치 = 해당 분야에서 물고기 잡았을 때 아이템이 지급될 확률 (최대값 1.0 = 100%)</li>
  68. <li>예: 0.8 = 80% 확률로 아이템 드롭. 분야별 난이도/희소성에 맞춰 신중히 설정</li>
  69. </ul>
  70. </div>
  71. <!-- 버튼 영역 -->
  72. <div class="admin--form-actions">
  73. <button type="button" class="admin--btn" @click="goToDetail">
  74. ← 취소
  75. </button>
  76. <button type="submit" class="admin--btn admin--btn-red ml--auto" :disabled="isSaving">
  77. {{ isSaving ? "저장 중..." : "저장" }}
  78. </button>
  79. </div>
  80. <!-- 성공/에러 메시지 -->
  81. <div v-if="successMessage" class="admin--alert admin--alert-success">
  82. {{ successMessage }}
  83. </div>
  84. <div v-if="errorMessage" class="admin--alert admin--alert-error">
  85. {{ errorMessage }}
  86. </div>
  87. </form>
  88. </div>
  89. </template>
  90. <script setup>
  91. import { ref, onMounted } from "vue";
  92. import { useRoute, useRouter } from "vue-router";
  93. definePageMeta({
  94. layout: "admin",
  95. middleware: ["auth"],
  96. });
  97. const route = useRoute();
  98. const router = useRouter();
  99. const { get, put } = useApi();
  100. const fieldId = route.params.id;
  101. const isSaving = ref(false);
  102. const successMessage = ref("");
  103. const errorMessage = ref("");
  104. const formData = ref({
  105. name: "",
  106. weight: "",
  107. status_YN: "Y",
  108. });
  109. // 가중치 형식: 0.0 ~ 1.0, 소수점 1자리
  110. const WEIGHT_PATTERN = /^(0(\.\d)?|1(\.0)?)$/;
  111. // 기존 데이터 로드
  112. const loadDetail = async () => {
  113. const { data, error } = await get(`/field/${fieldId}`);
  114. if (error || !data?.success) {
  115. errorMessage.value = error?.message || data?.message || "조회에 실패했습니다.";
  116. return;
  117. }
  118. const row = data.data || {};
  119. formData.value = {
  120. name: row.name ?? "",
  121. weight: row.weight ?? "",
  122. status_YN: row.status_YN ?? "Y",
  123. };
  124. };
  125. // 폼 제출
  126. const handleSubmit = async () => {
  127. successMessage.value = "";
  128. errorMessage.value = "";
  129. const name = formData.value.name.trim();
  130. const weight = formData.value.weight.trim();
  131. const status_YN = formData.value.status_YN;
  132. if (!name) return (errorMessage.value = "분야명을 입력하세요.");
  133. if (name.length > 30) return (errorMessage.value = "분야명은 30자 이내로 입력하세요.");
  134. if (!weight) return (errorMessage.value = "가중치를 입력하세요.");
  135. if (!WEIGHT_PATTERN.test(weight))
  136. return (errorMessage.value = "가중치는 0.0 ~ 1.0 사이 소수점 1자리로 입력하세요.");
  137. isSaving.value = true;
  138. try {
  139. const { data, error } = await put(`/field/${fieldId}`, { name, weight, status_YN });
  140. if (error || !data?.success) {
  141. errorMessage.value = error?.message || data?.message || "수정에 실패했습니다.";
  142. } else {
  143. successMessage.value = data.message || "수정되었습니다.";
  144. setTimeout(() => {
  145. router.push(`/site-manager/field/detail/${fieldId}`);
  146. }, 800);
  147. }
  148. } catch (e) {
  149. errorMessage.value = "서버 오류가 발생했습니다.";
  150. console.error("Update error:", e);
  151. } finally {
  152. isSaving.value = false;
  153. }
  154. };
  155. // 이동
  156. const goToDetail = () => router.push(`/site-manager/field/detail/${fieldId}`);
  157. onMounted(() => {
  158. loadDetail();
  159. });
  160. </script>